﻿//
// Copyright (c) 2011 Canyala Innovation AB
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Reflection;

using Canyala.Lagoon.Reflection;
using Canyala.Lagoon.Functional;

namespace Canyala.Lagoon.Extensions
{
    public static class ReflectionExtensions
    {
        public static IEnumerable<T> GetCustomAttributes<T>(this PropertyInfo propertyInfo)
        {
            #region Requires
            Contract.Requires<ArgumentException>(propertyInfo != null, "propertyInfo");
            #endregion
            #region Ensures
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);
            #endregion

            return propertyInfo.GetCustomAttributes(typeof(T), false).Cast<T>(); 
        }

        public static void SetValue(this PropertyInfo propertyInfo, object instance, object value)
        {
            #region Requires
            Contract.Requires<ArgumentException>(propertyInfo != null, "propertyInfo");
            Contract.Requires<ArgumentException>(instance != null, "instance");
            #endregion

            propertyInfo.SetValue(instance, value, null); 
        }

        public static object GetValue(this PropertyInfo propertyInfo, object instance)
        {
            #region Requires
            Contract.Requires<ArgumentException>(propertyInfo != null, "propertyInfo");
            Contract.Requires<ArgumentException>(instance != null, "instance");
            #endregion

            return propertyInfo.GetValue(instance, null); 
        }

        public static IEnumerable<SemanticProperty> GetSemanticProperties(this Type type)
        {
            #region Requires
            Contract.Requires<ArgumentException>(type != null, "type");
            #endregion

            return type.GetProperties().Select(property => SemanticProperty.FromPropertyInfo(property)); 
        }

        public static object GetPropertyValue(this object instance, SemanticProperty property)
        {
            #region Requires
            Contract.Requires<ArgumentException>(instance != null, "instance");
            Contract.Requires<ArgumentException>(property != null, "property");
            Contract.Requires(instance.GetType().GetProperty(property.Name, property.Type) != null);
            #endregion

            return instance.GetType().GetProperty(property.Name, property.Type).GetValue(instance);
        }

        public static object SetPropertyValue(this object instance, SemanticProperty property, object value)
        {
            #region Requires
            Contract.Requires<ArgumentException>(instance != null, "instance");
            Contract.Requires<ArgumentException>(property != null, "property");
            Contract.Requires(instance.GetType().GetProperty(property.Name, property.Type) != null);
            #endregion
            #region Ensures
            Contract.Ensures(Contract.Result<object>() != null);
            #endregion

            instance.GetType().GetProperty(property.Name, property.Type).SetValue(instance, value);
            return instance;
        }

        public static object SemanticCopy(this object target, object source)
        {
            #region Requires
            Contract.Requires<ArgumentException>(target != null, "target");
            Contract.Requires<ArgumentException>(source != null, "source");
            #endregion
            #region Ensures
            Contract.Ensures(Contract.Result<object>() != null);
            #endregion

            target
                .GetType()
                .GetSemanticProperties()
                .Intersect(source.GetType().GetSemanticProperties())
                .Do(property => target.SetPropertyValue(property, source.GetPropertyValue(property)));
            
            return target;
        }

        public static ICollection<TTarget> SemanticCopy<TTarget,TSource>(this ICollection<TTarget> collection, IEnumerable<TSource> seq)
            where TTarget : new()
        {
            #region Requires
            Contract.Requires<ArgumentException>(collection != null, "list");
            Contract.Requires<ArgumentNullException>(seq != null, "seq");
            #endregion
            #region Ensures
            Contract.Ensures(Contract.Result<ICollection<TTarget>>() != null);
            #endregion

            foreach (var item in seq)
            {
                TTarget targetItem = new TTarget();
                targetItem.SemanticCopy(item);
                collection.Add(targetItem);
            }

            return collection;
        }
        
        public static bool SemanticEquals(this object x, object y )
        {
            #region Requires
            Contract.Requires<ArgumentNullException>(x != null, "x");
            Contract.Requires<ArgumentNullException>(y != null, "y");
            #endregion

            return !x
                .GetType()
                .GetSemanticProperties()
                .Intersect(y.GetType().GetSemanticProperties())
                .Any(property => Equals(x.GetPropertyValue(property), y.GetPropertyValue(property)) == false);
        }

        public static bool SemanticEquals<TOne, TTwo>(this IEnumerable<TOne> x, IEnumerable<TTwo> y)
        {
            #region Requires
            Contract.Requires<ArgumentNullException>(x != null, "x");
            Contract.Requires<ArgumentNullException>(y != null, "y");
            #endregion

            return Seq.DoWhile(x, y, (a, b) => a.SemanticEquals(b));
        }
    }
}
